/* [n_primary]
 *********************
 * Things to remember:
 *
 * - detail is threadsafe, use caution, all changes will be seen by all threads; recommend: only use to .get data never .set
 * - global variables are global to all threads, use load & unload to initialize & free up memory
 * - use global isInit to initialize global variables requiring detail to be setup
 * - when using global isInit, the code must be mutex protected so all threads do not run the init, just one
 * - inter-thread coordination can be coordinated via the provided mutex
 */
#define DLL_EXPORT
#include "start.h"
#include "Database.h"
#include "NetworkManager.h"
#include "BNBase.h"

#ifdef _WIN32
#ifdef _DEBUG
	#define _CRTDBG_MAP_ALLOC
	#include <stdlib.h>
	#include <crtdbg.h>
	#undef THIS_FILE
	static char THIS_FILE[] = __FILE__;
	#define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
	#define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif

#include <iostream>
using namespace std;

#ifdef __cplusplus
extern "C" {
#endif	/*__cplusplus*/


// default global variables within dll
ParamManager *supported = NULL;
// module-specific global variables within dll
bool isInit = false;
Database *db = NULL;


// called after the module is loaded, useful to load global variables; Returns supported actions as a ParamManager*
DECLDIR ParamManager* load(void) {
	supported = new ParamManager();
	supported->setOverwrite(false);
	supported->set("action", "primary");

	return supported;
}


// called just before the module is unloaded, useful to unload global variables
DECLDIR void unload(void) {
	if(supported) {
		delete supported;
	}
	if(db) {
		delete db;
	} 
}


void init(HANDLE mutex, ParamManager &detail) {
	MYSQL_RES *rUpdate = NULL;

	// ensure we only init once
	if(!isInit) {
		char query[DEFAULT_BUFLEN];

		// Instance Table
		db = new Database(
					detail.get("db_user"), detail.get("db_pass"),
					detail.get("db_name"),
					detail.get("db_host"), atoi(detail.get("db_port")));

		// re-establish mysql connection if lost
		bool connect = true;
		mysql_options(&(db->conn), MYSQL_OPT_RECONNECT, &connect);

		sprintf_s(query, DEFAULT_BUFLEN, "CREATE TABLE IF NOT EXISTS n_primary (");
		strcat_s(query, DEFAULT_BUFLEN, "id INT NOT NULL AUTO_INCREMENT PRIMARY KEY");
		strcat_s(query, DEFAULT_BUFLEN, ", state INT DEFAULT 0");
		strcat_s(query, DEFAULT_BUFLEN, ", isPrime BOOL DEFAULT 1");
		strcat_s(query, DEFAULT_BUFLEN, ", isIgnore BOOL DEFAULT 0");
		strcat_s(query, DEFAULT_BUFLEN, ")");

		// init is wrapped by mutex, allowing safe mysql call here
		db->threadsafe_query(mutex, query);

		isInit = true;
	}
}


DECLDIR void start(HANDLE mutex, ParamManager &detail, char *index) {
	char query[DEFAULT_BUFLEN];
	MYSQL_RES *rSelect, *rUpdate;
	MYSQL_ROW row;

	// mutex protected init sequence
	WaitForSingleObject(mutex, INFINITE);	// Request ownership of mutex
	init(mutex, detail);					// Init sequence
	ReleaseMutex(mutex);					// Release mutex for other threads

	// check cmd for action to take
	if(!strcmp(detail.get("action"), "add")) {
		// generate a task to process the next untested number
		// todo_count = number * number
		
		// look up the next test value
		sprintf_s(query, DEFAULT_BUFLEN, "SELECT id FROM n_primary WHERE state=0 LIMIT 1");
		rSelect = db->threadsafe_query(mutex, query);
		int num_rows = mysql_num_rows(rSelect);
		if(!num_rows) {
			// nothing found, add a test value
			sprintf_s(query, DEFAULT_BUFLEN, "INSERT INTO n_primary (state) VALUES(0)");
			printf("query = '%s'\n", query);
			rUpdate = db->threadsafe_query(mutex, query);
			db->safe_mysql_free_result(rUpdate);

			// look up the next test value
			sprintf_s(query, DEFAULT_BUFLEN, "SELECT id FROM n_primary WHERE state=0 LIMIT 1");
			rSelect = db->threadsafe_query(mutex, query);
		}

		// load up the next number to test
		row = mysql_fetch_row(rSelect);
		char *value = new char[strlen(row[0]) + 1];
		strcpy(value, row[0]);
		printf("n_primary, add value is %s\n", value);

		db->safe_mysql_free_result(rSelect);

		BIGNUM *len = BN_new();
		BIGNUM *length = BN_new();
		BIGNUM *todo_count = BN_new();
		BN_CTX *ctx = BN_CTX_new();

		BN_add_word(len, 2);
		BN_dec2bn(&(length), value);
		
		BN_exp(todo_count, length, len, ctx);
		char *s_todo_count = BN_bn2dec(todo_count);
		printf("n_primary, todo_count = %s\n", s_todo_count);


		// Now generate a new task with the provided details plus this todo_count
		NetworkManager nm;
		Connection *conn = nm.add();
		conn->settings.set("host", detail.get("db_host"));
		conn->settings.set("port", "2244");
		nm.start(conn);

		conn->write("cmd=task_add\n");
		ParamManager add;
		while(true) {
			// wait forever until network activity
			nm.waitForReady();

			// process the network activity
			bool finished = false;
			if(nm.isData()) {
				while(conn = nm.getDataConnection()) {
//					printf("recv: '%s'\n", conn->bm->getNext());
					if(strstr(conn->bm->getNext(), "cmd=task_add_reply")) {
						add.init(conn->bm->getNext(), "|");
						conn->bm->pop();
						finished = true;
						break;
					}
					conn->bm->pop();
				}
				if(finished) {
					break;
				}
			}
		}

		char data[DEFAULT_BUFLEN];
		int task_id = atoi(add.get("id"));

		sprintf(data, "cmd=task_set|id=%d|data=todo_count|value=%s\n", task_id, s_todo_count);					conn->write(data);

		sprintf(data, "cmd=task_set|id=%d|data=block_size|value=%s\n", task_id, detail.get("block_size"));		conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=timeout|value=%s\n", task_id, detail.get("timeout"));			conn->write(data);

		sprintf(data, "cmd=task_set|id=%d|data=db_user|value=%s\n", task_id, detail.get("db_user"));			conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=db_pass|value=%s\n", task_id, detail.get("db_pass"));			conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=db_name|value=%s\n", task_id, detail.get("db_name"));			conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=db_host|value=%s\n", task_id, detail.get("db_host"));			conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=db_port|value=%s\n", task_id, detail.get("db_port"));			conn->write(data);

		sprintf(data, "cmd=task_set|id=%d|data=todo|value=primary\n", task_id);									conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=action|value=start\n", task_id);									conn->write(data);
		sprintf(data, "cmd=task_set|id=%d|data=value|value=%s\n", task_id, value);								conn->write(data);

		sprintf(data, "cmd=task_start|id=%d\n", task_id);														conn->write(data);


		sprintf_s(query, DEFAULT_BUFLEN, "UPDATE n_primary SET state=1 WHERE id=\"%s\"", value);
		printf("query = '%s'\n", query);
		rUpdate = db->threadsafe_query(mutex, query);
		db->safe_mysql_free_result(rUpdate);
		printf("add complete\n");


		delete[] value;
		OPENSSL_free(s_todo_count);
		BN_CTX_free(ctx);
		BN_free(todo_count);
		BN_free(length);
		BN_free(len);
	}
	else if(!strcmp(detail.get("action"), "start")) {
		char query[DEFAULT_BUFLEN];
//		MYSQL_RES *rSelect;
//		MYSQL_ROW row;


//		printf("n_primary, value=%s, index=%s\n", detail.get("value"), index);

		// process a block of numbers to multiply looking to invalidate the potential primary number
		BNBase asdf(index, detail.get("value"), "2");
		BIGNUM *slider = asdf.reset();
//		printf("start:");
		do {
			if(slider) {
				char *tmp = BN_bn2dec(slider);
//				printf(" %s", tmp);
				OPENSSL_free(tmp);
			}
		}	while(slider = asdf.getNext());
//		printf("\n");


		// load up the two numbers to test
		BIGNUM *one = BN_new();
		BN_copy(one, asdf.reset());
		BN_add_word(one, 1);
		BIGNUM *two = BN_new();
		BN_copy(two, asdf.getNext());
		BN_add_word(two, 1);
		BN_CTX *ctx = BN_CTX_new();

		// avoid multiplying against '1'
		if(BN_is_word(one, 1)) {
			BN_CTX_free(ctx);
			BN_free(one);
			BN_free(two);
			return;
		}
		if(BN_is_word(two, 1)) {
			BN_CTX_free(ctx);
			BN_free(one);
			BN_free(two);
			return;
		}

		// multiply the numbers together
		BIGNUM *result = BN_new();
		BN_add_word(result, 0);
		BN_mul(result, one, two, ctx);

		// check if the result matches the value we are trying to disprove
		char *tmp = BN_bn2dec(result);
		if(!strcmp(tmp, detail.get("value"))) {
			// this number is not prime
			printf("*** NOT PRIME ***\n");
			sprintf_s(query, DEFAULT_BUFLEN, "UPDATE n_primary SET isPrime=0, state=2, isIgnore=1 WHERE id=\"%s\"", detail.get("value"));
			rUpdate = db->threadsafe_query(mutex, query);
			db->safe_mysql_free_result(rUpdate);
		}
		OPENSSL_free(tmp);

		// free up the resources
		BN_CTX_free(ctx);
		BN_free(one);
		BN_free(two);
	}

	// free up mysql thread-specific memory
	mysql_thread_end();
}


#ifdef __cplusplus
}
#endif	/*__cplusplus */